#ifndef __CRYLOBBY_H__
#define __CRYLOBBY_H__

#pragma once

#define TO_GAME_FROM_LOBBY static_cast<CCryLobby*>(CNetwork::Get()->GetLobby())->GetToGameQueue()->Add
#define FROM_GAME_TO_LOBBY static_cast<CCryLobby*>(CNetwork::Get()->GetLobby())->GetFromGameQueue()->Add




#define USE_XBOXLIVE	0


#define USE_GFWL			0
#if USE_GFWL
#include <xtl.h>                    // StdAfx.h cannot include xtl.h, because USE_GFWL is set here.
#endif





#define USE_PSN					0
#define USE_PSN_VOICE		0	


#define USE_LIVE			(USE_XBOXLIVE || USE_GFWL)

#define RESET_CONNECTED_CONNECTION (USE_CRYLOBBY_GAMESPY)

#include "Config.h"
#include "ICryLobby.h"
#include "Socket/IDatagramSocket.h"
#include "NetTimer.h"
#include "Network.h"
#include "CryRebroadcaster.h"
#include "CryLobbyPacket.h"

#include "CryTCPServiceFactory.h"
#include "ICryTCPService.h"

#if ( USE_CRYLOBBY_GAMESPY && USE_GFWL )
#error "USE_CRYLOBBY_GAMESPY and USE_GFWL are both enabled - only one option can be enabled"
#endif

enum CryLobbyPacketType
{
	eLobbyPT_Ack,
	eLobbyPT_SessionDeleteRemoteConnection,
#if NETWORK_HOST_MIGRATION
	eLobbyPT_HostHinting,
#if ENABLE_HOST_MIGRATION_STATE_CHECK
	eLobbyPT_HostMigrationStateCheckData,
#endif
#endif
	eLobbyPT_ServerPing,
	eLobbyPT_ConnectionRequest,
	eLobbyPT_ConnectionRequestResult,
	eLobbyPT_Ping,
	eLobbyPT_Pong,
	eLobbyPT_VoiceMicrophoneRequest,
	eLobbyPT_VoiceMicrophoneNotification,
	eLobbyPT_SessionRequestJoinResult,
	eLobbyPT_InvalidPackeType,
	eLobbyPT_EndType
};

#define CRYLOBBY_PACKET_TYPE_ENCODE_SEED	0x5eed5eed

#define CRYSHAREDLOBBY_PACKET_START		0
#define CRYSHAREDLOBBY_PACKET_END			(eLobbyPT_EndType - 1)
#define CRYONLINELOBBY_PACKET_START		(CRYSHAREDLOBBY_PACKET_END + 1)
#define CRYONLINELOBBY_PACKET_MAX			63
#define CRYLANLOBBY_PACKET_START			(CRYONLINELOBBY_PACKET_MAX + 1)
#define CRYLANLOBBY_PACKET_MAX				127

const uint32 CryLobbyTimeOut = 2000;
const uint32 CryLobbySendInterval = 200;

typedef uint32 CryPingAccumulator;

#define MAX_LOBBY_PACKET_SIZE		1200	// Same as fragment size so lobby packets never fragment.
#define MAX_LOBBY_CONNECTIONS		64
#define MAX_LOBBY_TASKS					10
#define MAX_LOBBY_TASK_DATAS		10
#define SEND_DATA_QUEUE_SIZE		32
#define NUM_LOBBY_PINGS					3
#define MAX_SOCKET_PORTS_TRY		16u

class CCryLobby;

typedef uint32 CryLobbyServiceTaskID;
const CryLobbyServiceTaskID CryLobbyServiceInvalidTaskID = 0xffffffff;

typedef uint32 CryLobbySendID;
const CryLobbySendID CryLobbyInvalidSendID = 0xffffffff;

typedef void (*CryLobbyServiceCallback)(ECryLobbyError error, CCryLobby* arg, ECryLobbyService service);

class ICryLobbyGFWLExtras
{
public:
	virtual ~ICryLobbyGFWLExtras(){}
	virtual void		OnCreateDevice(void* pD3D, void* pD3DPP) = 0;
	virtual void		OnDeviceReset(void* pD3DPP) = 0;
	virtual void		OnDeviceDestroyed() = 0;
	virtual void		Render() = 0;
	virtual uint32	PreTranslateMessage(void* msg) = 0;
};

enum ECryLobbyConnectionState
{
	eCLCS_NotConnected,
	eCLCS_Pending,
	eCLCS_Connected,
	eCLCS_Freeing
};

class CCryLobbyService : public CMultiThreadRefCount, public ICryLobbyService
{
public:
														CCryLobbyService(CCryLobby* pLobby, ECryLobbyService service);
	virtual										~CCryLobbyService();

	virtual ECryLobbyError		Initialise(ECryLobbyServiceFeatures features, CryLobbyServiceCallback pCB);
	virtual ECryLobbyError		Terminate(ECryLobbyServiceFeatures features, CryLobbyServiceCallback pCB);

	virtual ICryTCPServicePtr	GetTCPService(const char* pUrl, uint16 port);
	virtual	ECryLobbyError		GetUserPrivileges(uint32 user, CryLobbyTaskID* pTaskID, CryLobbyPrivilegeCallback pCB, void* pCBArg);
	virtual	ECryLobbyError		GetUserCredentials( uint32 user, CryLobbyTaskID* pTaskID, CryLobbyGetUserCredentialsCallback pCB, void* pCBArg ) { return eCLE_ServiceNotSupported; };
	virtual ECryLobbyError		UserSignOut( uint32 user, CryLobbyTaskID* pTaskID, CryLobbyOnlineStateCallback pCb, void* pCbArg ) { return eCLE_ServiceNotSupported; };
	virtual ECryLobbyError		CheckProfanity( const char* const pString, CryLobbyTaskID* pTaskID, CryLobbyCheckProfanityCallback pCb, void* pCbArg ) { return eCLE_ServiceNotSupported; };

	virtual void							Tick(CTimeValue tv) = 0;

	virtual void							OnPacket(const TNetAddress& addr, CCryLobbyPacket* pPacket) = 0;
	virtual void							OnError(const TNetAddress& addr, ESocketError error, CryLobbySendID sendID) = 0;
	virtual void							OnSendComplete(const TNetAddress& addr, CryLobbySendID sendID) = 0;

	virtual bool							IsDead()const	{ return false; };

	virtual bool							GetFlag( ECryLobbyServiceFlag flag );
	virtual void							OnInternalSocketChanged( IDatagramSocketPtr pSocket ) {};
	
	virtual void							GetSocketPorts(uint16& connectPort,uint16& listenPort);
	virtual void							CancelTask(CryLobbyTaskID lTaskID);

					void							CreateSocketNT(void);
					void							FreeSocketNT(void);

	virtual ICryDLCStore*			GetDLCStore() { return NULL; }

protected:
	enum ETask
	{
		eT_GetUserPrivileges,
		eT_LobbyPlatformSpecificTask
	};

	struct  STask
	{
		uint32											user;
		CryLobbyTaskID							lTaskID;
		ECryLobbyError							error;
		uint32											startedTask;
		void*												pCB;
		void*												pCBArg;
		uint32											dataNum[MAX_LOBBY_TASK_DATAS];
		TMemHdl											dataMem[MAX_LOBBY_TASK_DATAS];
		bool												used;
		bool												running;
	};

	STask*												GetTask(CryLobbyServiceTaskID id)			{ return &m_tasks[id]; }
	ECryLobbyError								StartTask(uint32 eTask, uint32 user, bool startRunning, CryLobbyServiceTaskID* pLSTaskID, CryLobbyTaskID* pLTaskID, void* pCb, void* pCbArg);
	void													FreeTask(CryLobbyServiceTaskID lsTaskID);
	void													UpdateTaskError(CryLobbyServiceTaskID lsTaskID, ECryLobbyError error);
	void													StartTaskRunning(CryLobbyServiceTaskID lsTaskID);
	void													StopTaskRunning(CryLobbyServiceTaskID lsTaskID);
	void													EndTask(CryLobbyServiceTaskID lsTaskID);
	ECryLobbyError								CreateTaskParamMem( CryLobbyServiceTaskID lsTaskID, uint32 param, const void* pParamData, size_t paramDataSize );

	// In the future it would be good to move the common task handling code into CCryLobbyService and the common platform task handling code into the platform lobby services.
	// Because of this tasks should always be accessed via GetTask() as m_tasks will be removed when the platform task data gets added.
	STask													m_tasks[MAX_LOBBY_TASKS];
	CCryLobby*										m_pLobby;
	ECryLobbyService							m_service;
	CCryTCPServiceFactoryPtr			m_pTCPServiceFactory;
	uint32												m_lobbyServiceFlags;
};

typedef _smart_ptr<CCryLobbyService> CCryLobbyServicePtr;

class CCryLobby : public ICryLobby, public IDatagramListener
#if NETWORK_HOST_MIGRATION
	, public IHostMigrationEventListener
#endif
{
public:
											CCryLobby();
											~CCryLobby();

	static ICryLobby*								GetLobby() { return m_lobby ? m_lobby : m_lobby = new CCryLobby; }

	virtual ECryLobbyError					Initialise(ECryLobbyService service, ECryLobbyServiceFeatures features, CryLobbyConfigurationCallback cfgCb, CryLobbyCallback cb, void* cbArg);
	virtual ECryLobbyError					Terminate(ECryLobbyService service, ECryLobbyServiceFeatures features, CryLobbyCallback cb, void* cbArg);
	virtual ECryLobbyError					ProcessEvents();
	virtual ECryLobbyService				SetLobbyService(ECryLobbyService service);
	virtual ECryLobbyService				GetLobbyServiceType()												{ return m_service; }
	virtual ICryLobbyService*				GetLobbyService()														{ return m_services[m_service]; }
	virtual ICryMatchMaking*				GetMatchMaking()														{ return m_services[m_service] ? m_services[m_service]->GetMatchMaking() : NULL; }
	virtual ICryVoice*							GetVoice()																	{ return m_services[m_service] ? m_services[m_service]->GetVoice() : NULL; }
	virtual ICryReward*							GetReward()																	{ return m_services[m_service] ? m_services[m_service]->GetReward() : NULL; }
	virtual ICryStats*							GetStats()																	{ return m_services[m_service] ? m_services[m_service]->GetStats() : NULL; }
	virtual ICryLobbyUI*						GetLobbyUI()																{ return m_services[m_service] ? m_services[m_service]->GetLobbyUI() : NULL; }
	virtual ICryFriends*						GetFriends()																{ return m_services[m_service] ? m_services[m_service]->GetFriends() : NULL; }
	virtual ICryFriendsManagement*	GetFriendsManagement()											{ return m_services[m_service] ? m_services[m_service]->GetFriendsManagement() : NULL; }
	virtual ICryDLCStore*						GetDLCStore()																{ return m_services[m_service] ? m_services[m_service]->GetDLCStore() : NULL; }
	virtual ICryLobbyService*				GetLobbyService(ECryLobbyService service)		{ assert(service<eCLS_NumServices); return m_services[service]; }
	virtual ICryLobbyGFWLExtras*		GetLobbyExtras()														{ return m_gfwlExtras; }

	virtual const SCryLobbyParameters&	GetLobbyParameters() const;
	virtual void										SetUserPacketEnd(uint32 end)									{ m_userPacketEnd = end; CalculatePacketTypeEncoding(); }

#if NETWORK_HOST_MIGRATION
	virtual bool										OnInitiate(SHostMigrationInfo& hostMigrationInfo, uint32& state);
	virtual bool										OnDisconnectClient(SHostMigrationInfo& hostMigrationInfo, uint32& state);
	virtual bool										OnDemoteToClient(SHostMigrationInfo& hostMigrationInfo, uint32& state);
	virtual bool										OnPromoteToServer(SHostMigrationInfo& hostMigrationInfo, uint32& state);
	virtual bool										OnReconnectClient(SHostMigrationInfo& hostMigrationInfo, uint32& state);
	virtual bool										OnFinalise(SHostMigrationInfo& hostMigrationInfo, uint32& state);
	virtual bool										OnTerminate(SHostMigrationInfo& hostMigrationInfo, uint32& state);
	virtual bool										OnReset(SHostMigrationInfo& hostMigrationInfo, uint32& state);
					uint8										GetActiveConnections(void);
#endif

	virtual void										RegisterEventInterest(ECryLobbySystemEvent eventOfInterest, CryLobbyEventCallback cb, void *pUserData);
	virtual void										UnregisterEventInterest(ECryLobbySystemEvent eventOfInterest, CryLobbyEventCallback cb, void *pUserData);

	// Description:
	//	 Is the given flag true for the lobby service of the given type?
	// Arguments:
	//	 service - lobby service to be queried
	//	 flag - flag to be queried
	// Return:
	//	 True if the flag is true.
	virtual bool GetLobbyServiceFlag( ECryLobbyService service, ECryLobbyServiceFlag flag );
	
	virtual void SetCryEngineLoadHint(const char* pLevelName,const char* pGameRules) {strcpy_s(m_lvlName,pLevelName); strcpy_s(m_rulName,pGameRules);}
	virtual const char* GetCryEngineLevelNameHint() {return m_lvlName;}
	virtual const char* GetCryEngineRulesNameHint() {return m_rulName;}

	void								DispatchEvent(ECryLobbySystemEvent evnt, UCryLobbyEventData data);

	void								GetConfigurationInformation(SConfigurationParams* infos,uint32 infoCnt);

	void								InviteAccepted(ECryLobbyService service, uint32 user, CrySessionID sessionID, ECryLobbyError error);

	CWorkQueue*					GetToGameQueue()		{ return &m_toGameQueue; }
	CWorkQueue*					GetFromGameQueue()	{ return &m_fromGameQueue; }

	CryLobbyTaskID			CreateTask();
	void								ReleaseTask(CryLobbyTaskID id);
#if ENABLE_CRYLOBBY_DEBUG_TESTS
	bool								DebugGenerateError(CryLobbyTaskID id, ECryLobbyError& error);
	bool								DebugOKToStartTaskRunning(CryLobbyTaskID id);
	bool								DebugTickCallStartTaskRunning(CryLobbyTaskID id);
	bool								DebugOKToTickTask(CryLobbyTaskID id, bool running);
#endif

	void								InternalSocketCreate(ECryLobbyService service);
	void								InternalSocketFree(ECryLobbyService service);
	uint16							GetInternalSocketPort(ECryLobbyService service);
	IDatagramSocketPtr	GetInternalSocket(ECryLobbyService service);
	IDatagramSocketPtr	ExternalSocketCreate(const TNetAddress& addr, uint32 flags);
	void								ExternalSocketSetListener(IDatagramSocketPtr socket, IDatagramListener* listener);
	void								ExternalSocketDie(IDatagramSocketPtr socket);
	ESocketError				Send(CCryLobbyPacket* pPacket, const TNetAddress& to, CryLobbyConnectionID connectionID, CryLobbySendID* pSendID);
	ESocketError				SendVoice(CCryLobbyPacket* pPacket, const TNetAddress& to);
	CryLobbyConnectionID	CreateConnection(const TNetAddress& address);
	CryLobbyConnectionID	FindConnection(const TNetAddress& address);
	void								FreeConnection(CryLobbyConnectionID c);
	void								ConnectionAddRef(CryLobbyConnectionID c);
	void								ConnectionRemoveRef(CryLobbyConnectionID c);
	bool								ConnectionHasReference(CryLobbyConnectionID c);
	void								ConnectionSetState(CryLobbyConnectionID c, ECryLobbyConnectionState state);
	ECryLobbyConnectionState ConnectionGetState(CryLobbyConnectionID c);
	bool								ConnectionFromAddress(CryLobbyConnectionID* connection, const TNetAddress& address);
	bool								AddressFromConnection(TNetAddress& address, CryLobbyConnectionID connection);
	bool								SetConnectionAddress(CryLobbyConnectionID connection, TNetAddress& address);
	CryPing							GetConnectionPing(CryLobbyConnectionID connectionID);
	void								FlushMessageQueue(void);
	void								SetNATType(ENatType natType)	{ m_natType = natType;	}
	ENatType						GetNATType(void) const				{ return m_natType;			}

	//start of IDatagramListener
	virtual void				OnPacket(const TNetAddress& addr, const uint8* pData, uint32 length);
	virtual void				OnError(const TNetAddress& addr, ESocketError error);
	//end of IDatagramListener
	void								OnSendComplete(const TNetAddress& addr);
	void								ProcessPacket(const TNetAddress& addr, CryLobbyConnectionID connectionID, CCryLobbyPacket* pPacket);

	CryLockT<CRYLOCK_RECURSIVE>& GetMutex()													{ return m_mutex; }
	void								MutexLock()																	{ m_mutex.Lock(); }
	bool								MutexTryLock()															{ return m_mutex.TryLock(); }
	void								MutexUnlock()																{ m_mutex.Unlock(); }
	TMemHdl							MemAlloc(size_t sz)													{ SCOPED_GLOBAL_LOCK; return m_MMM ? m_MMM->AllocHdl(sz) : TMemInvalidHdl; }
	void								MemFree(TMemHdl h)													{ SCOPED_GLOBAL_LOCK; if (m_MMM) { m_MMM->FreeHdl(h); } }
	void*								MemGetPtr(TMemHdl h)												{ return m_MMM ? m_MMM->PinHdl(h) : NULL; }

	CCryRebroadcaster*	GetRebroadcaster(void)											{ return m_pRebroadcaster; }

	uint32							TimeSincePacketInMS( CryLobbyConnectionID c ) const;
	void								ForceTimeoutConnection(CryLobbyConnectionID c);
	void								SetServicePacketEnd(ECryLobbyService service, uint32 end)		{ m_servicePacketEnd[service] = end; CalculatePacketTypeEncoding(); }
	const TNetAddress*	GetNetAddress(CryLobbyConnectionID c) const;

private:

	struct SSocketService																		// Ideally we could move these into the services themselves, but maybe this will result in
	{																												//less duplication. For now, a seperate socket will only be created if the socketTearDown flag
		IDatagramSocketPtr	m_socket;													//is not set for the service. To allow this to work, services that don't set the flag are
		IDatagramListener*	m_pExternalSocketListener;				//required to use unique ports.
		uint16							m_socketListenPort;
		uint16							m_socketConnectPort;
	};

	SSocketService*			GetCorrectSocketService(ECryLobbyService serviceType);

	static	void				TimerCallback(NetTimerId id, void *arg, CTimeValue tv);
	void								TimerCallback(CTimeValue tv);

	static void					InitialiseServiceCB(ECryLobbyError error, CCryLobby* arg, ECryLobbyService service);
	static void					TerminateServiceCB(ECryLobbyError error, CCryLobby* arg, ECryLobbyService service);
	void								InternalSocketDie(ECryLobbyService service);
	ECryLobbyError			CheckAllocGlobalResources();
	void								CheckFreeGlobalResources();
	uint32							GetDisconnectTimeOut();
	void								UpdateConnectionPing(CryLobbyConnectionID connectionID, CryPing ping);
	bool								AddPacketToReliableBuildPacket(CryLobbyConnectionID connectionID, CCryLobbyPacket* pPacket);
	bool								AddReliablePacketToSendQueue(CryLobbyConnectionID connectionID, CCryLobbyPacket* pPacket);
	void								LogPacketsInBuffer(const uint8* pBuffer, uint32 size);
	bool								GetNextPacketFromBuffer(const uint8* pData, uint32 dataSize, uint32& dataPos, CCryLobbyPacket* pPacket);
	void	CalculatePacketTypeEncoding();
	void								EncodePacketDataHeader(CCryLobbyPacket* pPacket);
	void								DecodePacketDataHeader(CCryLobbyPacket* pPacket);

	static ICryLobby*		m_lobby;

	char		m_lvlName[128];
	char		m_rulName[128];

	struct SEventCBData
	{
		ECryLobbySystemEvent	event;
		CryLobbyEventCallback	cb;
		void									*pUserData;

		bool operator==(const SEventCBData &other) const {return (this->event == other.event && this->cb == other.cb && this->pUserData == other.pUserData);}
	};

	typedef std::vector<SEventCBData> EventCBList;

	CTimeValue					m_lastTickTime;
	EventCBList					m_callbacks;

	ECryLobbyService		m_service;
	CCryLobbyServicePtr	m_services[eCLS_NumServices];
	SCryLobbyParameters	m_serviceParams[eCLS_NumServices];

	ICryLobbyGFWLExtras*	m_gfwlExtras;

	struct STask
	{
		CryLobbyCallback	cb;
		void*							cbArg;
	}										m_task[eCLS_NumServices];

	struct SConnection 
	{
#if RESET_CONNECTED_CONNECTION
		uint64						sendCookie;
		uint64						recvCookie;
#endif
		uint32						timeSinceSend;
		uint32						timeSinceRecv;
		uint32						disconnectTimer;
		TNetAddress				addr;

		uint8							reliableBuildPacketBuffer[MAX_LOBBY_PACKET_SIZE];
		CCryLobbyPacket		reliableBuildPacket;

		struct SData
		{
			TMemHdl					data;
			uint16					dataSize;
			uint8						counter;
		};

		MiniQueue<SData, SEND_DATA_QUEUE_SIZE>	dataQueue;
		ECryLobbyConnectionState	state;

		struct SPing
		{
			CryPing					aveTime;
			CryPing					times[NUM_LOBBY_PINGS];
			CryPing					currentTime;
		}									ping;

		uint8							refCount;
		uint8							counterIn;
		uint8							counterOut;
		bool							used;
	}										m_connection[MAX_LOBBY_CONNECTIONS];

	struct SServiceTask
	{
		bool							used;

#if ENABLE_CRYLOBBY_DEBUG_TESTS
		CTimeValue				startTaskTime;
		CTimeValue				tickTaskTime;
		bool							generateErrorDone;
		bool							startTaskTimerStarted;
		bool							startTaskDone;
		bool							tickTaskTimerStarted;
		bool							tickTaskDone;
#endif
	}										m_serviceTask[MAX_LOBBY_TASKS];

	CWorkQueue					m_toGameQueue;
	CWorkQueue					m_fromGameQueue;

	CMementoMemoryManager*	m_MMM;
	CCryRebroadcaster*			m_pRebroadcaster;
	CryLockT<CRYLOCK_RECURSIVE>	m_mutex;
	NetTimerId							m_updateTimer;
	CryLobbyConfigurationCallback m_configCB;		// allows platform specific configuration to be requested by any attached service
	ENatType						m_natType;
	
	SSocketService				m_socketServices[eCLS_NumServices];

	uint16							m_servicePacketEnd[eCLS_NumServices];
	uint16							m_userPacketEnd;
	uint8								m_encodePacketType[256];
	uint8								m_decodePacketType[256];

	enum ELobbyHostMigrationStatus
	{
		eLHMS_InitiateMigrate,
		eLHMS_Migrating
	};
};

struct SCrySharedUserID : public SCryUserID
{
	void* operator new(size_t size) throw()
	{
		CCryLobby* pLobby = (CCryLobby*)CCryLobby::GetLobby();

		if (pLobby)
		{
			TMemHdl hdl = pLobby->MemAlloc(size);

			if (hdl != TMemInvalidHdl)
			{
				SCrySharedUserID* id = (SCrySharedUserID*)pLobby->MemGetPtr(hdl);

				id->hdl = hdl;

				return id;
			}
		}

		return NULL;
	}

	void operator delete(void* p)
	{
		if (p)
		{
			CCryLobby* pLobby = (CCryLobby*)CCryLobby::GetLobby();

			if (pLobby)
			{
				pLobby->MemFree(((SCrySharedUserID*)p)->hdl);
			}
		}
	}

	TMemHdl	hdl;
};

#define LOBBY_AUTO_LOCK AUTO_LOCK_T(CryLockT<CRYLOCK_RECURSIVE>, ((CCryLobby*)CCryLobby::GetLobby())->GetMutex())

inline CryLobbySendID CryLobbyCreateSendID(CryLobbyConnectionID connection, uint32 count)
{
	return connection|(count<<16);
}

inline CryLobbyConnectionID CryLobbyGetConnectionIDFromSendID(CryLobbySendID id)
{
	return id&0xffff;
}
#endif // __CRYLOBBY_H__

